using System.Collections.Generic;
using Unity.Collections;
using UnityEditor;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;

public class IVGIBaker : MonoBehaviour
{
    public GameObject _MeshRoot;
    private GameObject _CombinedMesh;
    
    public LightProbeGroup _LightProbeGroup;
    public int _LightProbeGroupDensity = 2;
    public Vector3 _VolumeOrigin = Vector3.zero;
    public int _VolumeSize = 100;

    public Texture3D _IVGILightMap3D = null;
    public Texture2D _IVGILightMap2D = null;
    public RenderTexture _ShadowMapRT = null;
    public Texture3D _ShadowMap3D = null;
    public int _TextureSize = 100;

    public Material _MaterialLit;
    public Material _MaterialIVGI;
    
    public bool showLightProbeVolume;

    public ComputeShader shadowCompute;
    
    public void OnDrawGizmos()
    {
        if (showLightProbeVolume)
        {
            Vector3 size = new Vector3(_VolumeSize,_VolumeSize,_VolumeSize);
            Vector3 center = _VolumeOrigin + size / 2;
            Gizmos.DrawWireCube(center, size);
        }
    }
    
    public void PlacementProbe()
    {
        if (_LightProbeGroup == null)
        {
            _LightProbeGroup = GetComponentInChildren<LightProbeGroup>();
        }
        
        if (_LightProbeGroup == null)
        {
            GameObject go = new GameObject("LightProbeGroup");
            go.transform.position = Vector3.one;
            go.transform.rotation = Quaternion.identity;
            go.transform.localScale = Vector3.one;
            go.transform.parent = this.gameObject.transform;
            _LightProbeGroup = go.AddComponent<LightProbeGroup>();
        }
        else
        {
#if UNITY_EDITOR
            _LightProbeGroup.probePositions = null;
#endif
        }
        
        List<Vector3> pos = new List<Vector3>();

        // <= 边缘多加一组
        for (float x = 0; x <= _VolumeSize; x += _LightProbeGroupDensity)
        {
            for (float y = 0; y <= _VolumeSize; y += _LightProbeGroupDensity)
            {
                for (float z = 0; z <= _VolumeSize; z += _LightProbeGroupDensity)
                {
                    Vector3 curPos = new Vector3(x + _VolumeOrigin.x, y + _VolumeOrigin.y, z + _VolumeOrigin.z);
                    //if (Physics.CheckSphere(curPos, radiusMax) && !Physics.CheckSphere(curPos, radiusMin))
                    {
                        pos.Add(curPos);
                    }
                }
            }
        }
#if UNITY_EDITOR
        _LightProbeGroup.probePositions = pos.ToArray();
#endif
    }
    
    public void CombineMesh()
    {
        if (_MeshRoot == null)
        {
            return;
        }
        
        if (_CombinedMesh == null)
        {
            _CombinedMesh = new GameObject("_CombinedMesh");
            _CombinedMesh.transform.position = Vector3.one;
            _CombinedMesh.transform.rotation = Quaternion.identity;
            _CombinedMesh.transform.localScale = Vector3.one;
            _CombinedMesh.transform.parent = this.gameObject.transform;
        }
        
        if (_CombinedMesh.GetComponent<MeshFilter>() == null)
        {
            _CombinedMesh.AddComponent<MeshFilter>();
        }
        
        if (_CombinedMesh.GetComponent<MeshRenderer>() == null)
        {
            _CombinedMesh.AddComponent<MeshRenderer>();
        }
        
        if (_CombinedMesh.GetComponent<MeshCollider>() == null)
        {
            _CombinedMesh.AddComponent<MeshCollider>();
        }
        
        MeshFilter[] meshFilters = _MeshRoot.GetComponentsInChildren<MeshFilter>();
        CombineInstance[] combine = new CombineInstance[meshFilters.Length];
        Material[] mats = new Material[meshFilters.Length];
        Matrix4x4 matrix = transform.worldToLocalMatrix;
        for (int i = 0; i < meshFilters.Length; i++)
        {
            MeshFilter mf = meshFilters[i];
            MeshRenderer mr = meshFilters[i].GetComponent<MeshRenderer>();
            if (mr == null)
            {
                continue;
            }
            combine[i].mesh = mf.sharedMesh;
            combine[i].transform = mf.transform.localToWorldMatrix * matrix;
            mats[i] = mr.sharedMaterial;
        }

        Mesh mesh = new Mesh();
        mesh.name = "IVGI Baked Combined Mesh";
        mesh.CombineMeshes(combine, false);
        
        _CombinedMesh.GetComponent<MeshFilter>().mesh = mesh;
        _CombinedMesh.GetComponent<MeshRenderer>().sharedMaterials = mats;
        _CombinedMesh.GetComponent<MeshCollider>().sharedMesh = mesh;
    }

    public void BakeIVGI()
    {
        BakedShadowToVolume();

        int width = _ShadowMapRT.width, height = _ShadowMapRT.height, depth = _ShadowMapRT.volumeDepth;
        var shadowData = new NativeArray<byte>(width * height * depth * 8, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); //change if format is not 8 bits (i was using R8_UNorm) (create a struct with 4 bytes etc)
        AsyncGPUReadback.RequestIntoNativeArray(ref shadowData, _ShadowMapRT, 0, (_) =>
        {
            _ShadowMap3D = new Texture3D(width, height, depth, _ShadowMapRT.graphicsFormat, TextureCreationFlags.None);
            _ShadowMap3D.SetPixelData(shadowData, 0);
            _ShadowMap3D.Apply(updateMipmaps: false, makeNoLongerReadable: false);

            //先按场景尺寸，创建纹理
            _IVGILightMap3D = new Texture3D(_VolumeSize, _VolumeSize, _VolumeSize, TextureFormat.RGBAHalf, false);
            _IVGILightMap3D.name = "_IGVI_LightMap";
            _IVGILightMap3D.filterMode = FilterMode.Trilinear;
            _IVGILightMap3D.wrapMode = TextureWrapMode.Clamp;
            
            Color[,,] volume = FillVolume();

            volume = SmoothVolume(volume);

            //volume = Gaussian5(volume, true);

            for (int x = 0; x <= _VolumeSize; x++)
            for (int y = 0; y <= _VolumeSize; y++)
            for (int z = 0; z <= _VolumeSize; z++)
            {
                Color color = volume[x, y, z];
                
                _IVGILightMap3D.SetPixel(x, y, z, color);
            }

            _IVGILightMap3D.Apply();

            //目标纹理尺寸
            if (_VolumeSize != _TextureSize)
            {
                _IVGILightMap3D = ResizeTexture3D(_IVGILightMap3D, _TextureSize, _TextureSize, _TextureSize);
            }

            // Color[,,] C = new Color[51, 51, 51];
            //
            // for (int x = 0; x <= 50; x++)
            // for (int y = 0; y <= 50; y++)
            // for (int z = 0; z <= 50; z++)
            // {
            //     C[x,y,z] = _IVGILightMap3D.GetPixel(x, y, z);
            // }
            //
            // for (int i = 0; i < 5; i++)
            // {
            //     C =  ColorGaussian5(C);
            // }
            //
            // for (int x = 0; x <= 50; x++)
            // for (int y = 0; y <= 50; y++)
            // for (int z = 0; z <= 50; z++)
            // {
            //     _IVGILightMap3D.SetPixel(x, y, z, C[x, y, z]);
            // }
            
            _IVGILightMap3D.Apply();
            
            // AssetDatabase.CreateAsset(output, $"Assets/{pathWithoutAssetsAndExtension}.asset");
            // AssetDatabase.SaveAssetIfDirty(output);

            // a.Dispose();
        });
        
        // _ShadowMap3D = new Texture3D(_VolumeSize, _VolumeSize, _VolumeSize, TextureFormat.RGBAHalf, false);
        // RenderTexture.active = _ShadowMapRT;
        // _ShadowMap3D.ReadPixels(new Rect(0, 0, _VolumeSize, _VolumeSize), 0, 0);
        // _ShadowMap3D.Apply();
    }

    private Color[,,] FillVolume()
    {
        var probes = LightmapSettings.lightProbes;
        var positions = probes.positions;
        var shs = probes.bakedProbes;

        Vector3 min = _VolumeOrigin;
        Vector3 max = _VolumeOrigin + new Vector3(_VolumeSize + 1, _VolumeSize + 1, _VolumeSize + 1);

        var grid = new Color[(int)max.x, (int)max.y, (int)max.z];

        for (int i = 0; i < positions.Length; i++)
        {
            Vector3 pos = positions[i];
            int x = Mathf.FloorToInt(pos.x);
            int y = Mathf.FloorToInt(pos.y);
            int z = Mathf.FloorToInt(pos.z);

            // 确保在合法范围内
            if (x >= min.x && x <= max.x &&
                y >= min.y && y <= max.y &&
                z >= min.z && z <= max.z)
            {
                grid[x, y, z] = SHUtility.EvaluateL0(shs[i]);
                grid[x, y, z].a = _ShadowMap3D.GetPixel(x, y, z).r;
            }
            else
            {
                grid[x, y, z] = Color.clear;
            }
        }
        
        int mixRadius = (_LightProbeGroupDensity - 1) * 2;
        FillMissingVolumeWithVisibility(grid, mixRadius);
        
        return grid;
    }

    private void BakedShadowToVolume()
    {
        _ShadowMapRT = new RenderTexture(_VolumeSize, _VolumeSize, 0);
        _ShadowMapRT.dimension = UnityEngine.Rendering.TextureDimension.Tex3D;
        _ShadowMapRT.name = "_IGVI_ShadowMap3D";
        _ShadowMapRT.volumeDepth = _VolumeSize;
        _ShadowMapRT.enableRandomWrite = true;
        _ShadowMapRT.format = RenderTextureFormat.ARGBHalf;
        _ShadowMapRT.wrapMode = TextureWrapMode.Clamp;
        _ShadowMapRT.filterMode = FilterMode.Point;
        
        int kernel = shadowCompute.FindKernel("CSMain");    

        shadowCompute.SetTexture(kernel, "_Result", _ShadowMapRT);
        shadowCompute.SetInt("_VolumeSize", _VolumeSize);
        shadowCompute.SetVector("_VolumeOrigin",_VolumeOrigin);

        int groupSize = Mathf.CeilToInt(_VolumeSize / 10f);
        shadowCompute.Dispatch(kernel, groupSize, groupSize, groupSize);
    }
    
    private void FillMissingVolumeWithVisibility(Color[,,] volume, int mixRadius)
    {
        int sizeX = volume.GetLength(0);
        int sizeY = volume.GetLength(1);
        int sizeZ = volume.GetLength(2);

        for (int x = 0; x < sizeX; x++)
        for (int y = 0; y < sizeY; y++)
        for (int z = 0; z < sizeZ; z++)
        {
            if (!IsClearColor(volume[x, y, z]))
                continue;

            Vector3 pos = new Vector3(x, y, z);
            Color accum = new Color();
            float totalWeight = 0f;

            for (int dx = -mixRadius; dx <= mixRadius; dx++)
            for (int dy = -mixRadius; dy <= mixRadius; dy++)
            for (int dz = -mixRadius; dz <= mixRadius; dz++)
            {
                int nx = x + dx;
                int ny = y + dy;
                int nz = z + dz;

                // 边界检测
                if (nx < 0 || ny < 0 || nz < 0 ||
                    nx >= sizeX || ny >= sizeY || nz >= sizeZ)
                    continue;

                // 仅考虑 probe 点（2米间隔）
                if (nx % mixRadius != 0 || ny % mixRadius != 0 || nz % mixRadius != 0)
                    continue;

                Color color = volume[nx, ny, nz];
                if (IsClearColor(color))
                    continue;

                Vector3 probePos = new Vector3(nx, ny, nz);
                if (!IsVisible(pos, probePos))
                    continue;

                float dist = Vector3.Distance(pos, probePos);
                float weight = 1f / Mathf.Max(dist, 0.001f);

                AddColor(ref accum, color, weight);
                totalWeight += weight;
            }

            if (totalWeight > 0f)
            {
                UnityEngine.Debug.Log("color /= totalWeight, xyz:" + x + "," + y + "," + z + ", color:" + accum + "," + totalWeight);
                NormalizeColor(ref accum, totalWeight);
                volume[x, y, z] = accum;
            }
        }
    }

    private bool IsVisible(Vector3 from, Vector3 to)
    {
        Vector3 dir = to - from; //+ Vector3.one * 0.1f
        return !Physics.Raycast(from, dir.normalized, dir.magnitude);
    }
    
    private void NormalizeColor(ref Color color, float totalWeight)
    {
        if (totalWeight <= 0f) return;
        color /= totalWeight;
        // color.r /= totalWeight;
        // color.g /= totalWeight;
        // color.b /= totalWeight;
        // color.a /= totalWeight;
    }
    
    private void AddColor(ref Color target, Color source, float weight)
    {
        target.r += source.r * weight;
        target.g += source.g * weight;
        target.b += source.b * weight;
        target.a += source.a * weight;
    }
   
    private bool IsClearColor(Color color)
    {
        return color == Color.clear;
    }
    
    private Color[,,] SmoothVolume(Color[,,] volume, int iterCount = 3)
    {
        for (int i = 0; i < iterCount; i++) // 迭代3次
        {
            volume = Gaussian5(volume, true);
        }
        
        return volume;
    }

    public Color[,,] Gaussian5(Color[,,] volume, bool alpha = false)
    {
        int sizeX = volume.GetLength(0);
        int sizeY = volume.GetLength(1);
        int sizeZ = volume.GetLength(2);

        var result = new Color[sizeX, sizeY, sizeZ];

        for (int x = 0; x < sizeX; x++)
        for (int y = 0; y < sizeY; y++)
        for (int z = 0; z < sizeZ; z++)
        {
            Color accum = new Color();
            accum = volume[x, y, z];
            
            float totalWeight = 0f;

            Vector3 pos = new Vector3(x, y, z);
            
            for (int dx = -2; dx <= 2; dx++)
            for (int dy = -2; dy <= 2; dy++)
            for (int dz = -2; dz <= 2; dz++)
            {
                int nx = x + dx;
                int ny = y + dy;
                int nz = z + dz;

                if (nx < 0 || nx >= sizeX ||
                    ny < 0 || ny >= sizeY ||
                    nz < 0 || nz >= sizeZ)
                    continue;

                Vector3 probePos = new Vector3(nx, ny, nz);
                if (!IsVisible(pos,probePos))
                {
                    continue;
                }
                float wx = gaussian1D[dx + 2];
                float wy = gaussian1D[dy + 2];
                float wz = gaussian1D[dz + 2];
                float weight = wx * wy * wz;

                //AddColor(ref accum, volume[nx, ny, nz], weight);
               
                accum.r += volume[nx, ny, nz].r * weight;
                accum.g += volume[nx, ny, nz].g * weight;
                accum.b += volume[nx, ny, nz].b * weight;

                if (alpha)
                {
                    accum.a += volume[nx, ny, nz].a * weight;
                }
                
                totalWeight += weight;
            }

            //NormalizeColor(ref accum, totalWeight);
            accum.r /= totalWeight;
            accum.g /= totalWeight;
            accum.b /= totalWeight;

            if (alpha)
            {
                accum.a /= totalWeight;
            }

            result[x, y, z] = accum;
        }

        return result;
    }
    
    private Texture3D ResizeTexture3D(Texture3D source, int newWidth, int newHeight, int newDepth)
    {
        Texture3D result = new Texture3D(newWidth, newHeight, newDepth, TextureFormat.RGBA32, false);
        Color[] newColors = new Color[newWidth * newHeight * newDepth];

        Color[] sourceColors = source.GetPixels();
        int oldWidth = source.width;
        int oldHeight = source.height;
        int oldDepth = source.depth;

        RenderTexture rt = RenderTexture.GetTemporary(newWidth, newHeight, 0);
        RenderTexture.active = rt;

        // 用于临时传输贴图的 2D 贴图
        Texture2D tempSource2D = new Texture2D(oldWidth, oldHeight, TextureFormat.RGBA32, false);
        Texture2D tempResult2D = new Texture2D(newWidth, newHeight, TextureFormat.RGBA32, false);

        for (int z = 0; z < newDepth; z++)
        {
            // 源切片对应旧的 z（缩放到 0~1 再乘 oldDepth）
            float zRatio = (float)z / Mathf.Max(1, newDepth - 1);
            int oldZ = Mathf.Clamp(Mathf.RoundToInt(zRatio * (oldDepth - 1)), 0, oldDepth - 1);

            // 读取该层的颜色数据
            Color[] sliceColors = new Color[oldWidth * oldHeight];
            for (int y = 0; y < oldHeight; y++)
            {
                for (int x = 0; x < oldWidth; x++)
                {
                    int srcIndex = x + y * oldWidth + oldZ * oldWidth * oldHeight;
                    sliceColors[x + y * oldWidth] = sourceColors[srcIndex];
                }
            }

            tempSource2D.SetPixels(sliceColors);
            tempSource2D.Apply();

            // Blit 缩放
            Graphics.Blit(tempSource2D, rt);

            // 读取缩放后的颜色
            tempResult2D.ReadPixels(new Rect(0, 0, newWidth, newHeight), 0, 0);
            tempResult2D.Apply();

            Color[] resizedSlice = tempResult2D.GetPixels();

            // 写入结果 3D 贴图的对应层
            for (int y = 0; y < newHeight; y++)
            {
                for (int x = 0; x < newWidth; x++)
                {
                    int dstIndex = x + y * newWidth + z * newWidth * newHeight;
                    newColors[dstIndex] = resizedSlice[x + y * newWidth];
                }
            }
        }

        result.SetPixels(newColors);
        result.Apply();

        // 清理
        RenderTexture.ReleaseTemporary(rt);
        RenderTexture.active = null;

        return result;
    }

    public Texture2D Texture3DTo2D(Texture3D texture3D)
    {
        int w = texture3D.width;
        int h = texture3D.height;
        int d = texture3D.depth;
        
        int texWidth = w * h;
        int texHeight = d;
        
        Texture2D flatTex = new Texture2D(texWidth, texHeight, TextureFormat.RGBAHalf, false);
        flatTex.name = "_IGVI_LightMap2D";
        flatTex.filterMode = FilterMode.Trilinear;
        flatTex.wrapMode = TextureWrapMode.Clamp;
        flatTex.anisoLevel = 3;
        //int index = 0;
        for (int z = 0; z < d; z++)
        for (int y = 0; y < h; y++)
        for (int x = 0; x < w; x++)
        {
            int index1D = x + y * w + z * w * h;
            int u = index1D % texWidth;
            int v = index1D / texWidth;

            Color c = texture3D.GetPixel(x, y, z);
            flatTex.SetPixel(u, v, c);
        }
        
        flatTex.Apply();
        
        return flatTex;
    }
    
    #region By SH
    
    public void BakeSH2IVGI()
    {
        //先按场景尺寸，创建纹理
        _IVGILightMap3D = new Texture3D(_VolumeSize, _VolumeSize, _VolumeSize, TextureFormat.RGBAHalf, false);
        _IVGILightMap3D.name = "_IGVI_LightMap";
        _IVGILightMap3D.filterMode = FilterMode.Trilinear;
        _IVGILightMap3D.wrapMode = TextureWrapMode.Clamp;
        _IVGILightMap3D.anisoLevel = 3;

        SphericalHarmonicsL2[,,] gridSH = FillSH();

        //gridSH = SmoothSH(gridSH);
        
        for (int x = 0; x < _VolumeSize + 1; x++)
        for (int y = 0; y < _VolumeSize + 1; y++)
        for (int z = 0; z < _VolumeSize + 1; z++)
        {
            SphericalHarmonicsL2 sh = gridSH[x, y, z];
            var L0 = SHUtility.EvaluateL0(sh);
            
            _IVGILightMap3D.SetPixel(x, y, z, L0);
        }
        
        _IVGILightMap3D.Apply();
        
        //目标纹理尺寸
        if (_VolumeSize != _TextureSize)
        {
            _IVGILightMap3D = ResizeTexture3D(_IVGILightMap3D, _TextureSize, _TextureSize, _TextureSize);
        }
        
        _IVGILightMap2D = Texture3DTo2D(_IVGILightMap3D);
    }
    
    private SphericalHarmonicsL2[,,] FillSH()
    {
        var probes = LightmapSettings.lightProbes;
        var positions = probes.positions;
        var shs = probes.bakedProbes;

        Vector3 min = _VolumeOrigin;
        Vector3 max = _VolumeOrigin + new Vector3(_VolumeSize + 1, _VolumeSize + 1, _VolumeSize + 1);

        var grid = new SphericalHarmonicsL2[(int)max.x, (int)max.y, (int)max.z];

        for (int i = 0; i < positions.Length; i++)
        {
            Vector3 pos = positions[i];
            int x = Mathf.FloorToInt(pos.x);
            int y = Mathf.FloorToInt(pos.y);
            int z = Mathf.FloorToInt(pos.z);

            // 确保在合法范围内
            if (x >= min.x && x <= max.x &&
                y >= min.y && y <= max.y &&
                z >= min.z && z <= max.z)
            {
                grid[x, y, z] = shs[i];
            }
            else
            {
                grid[x, y, z] = new SphericalHarmonicsL2();
            }
        }

        int mixRadius = (_LightProbeGroupDensity - 1) * 2; 
        FillMissingSHWithVisibility(grid, mixRadius);
        
        return grid;
    }

    private SphericalHarmonicsL2[,,] SmoothSH(SphericalHarmonicsL2[,,] gridSH, int iterCount = 3)
    {
        for (int i = 0; i < iterCount; i++) // 迭代3次
        {
            gridSH = Gaussian5(gridSH);
        }
        
        return gridSH;
    }
    
    private void FillMissingSHWithVisibility(SphericalHarmonicsL2[,,] gridSH, int mixRadius)
    {
        int sizeX = gridSH.GetLength(0);
        int sizeY = gridSH.GetLength(1);
        int sizeZ = gridSH.GetLength(2);

        for (int x = 0; x < sizeX; x++)
        for (int y = 0; y < sizeY; y++)
        for (int z = 0; z < sizeZ; z++)
        {
            if (!IsEmptySH(gridSH[x, y, z]))
                continue;

            Vector3 pos = new Vector3(x, y, z);
            SphericalHarmonicsL2 accum = new SphericalHarmonicsL2();
            float totalWeight = 0f;

            for (int dx = -mixRadius; dx <= mixRadius; dx++)
            for (int dy = -mixRadius; dy <= mixRadius; dy++)
            for (int dz = -mixRadius; dz <= mixRadius; dz++)
            {
                int nx = x + dx;
                int ny = y + dy;
                int nz = z + dz;

                // 边界检测
                if (nx < 0 || ny < 0 || nz < 0 ||
                    nx >= sizeX || ny >= sizeY || nz >= sizeZ)
                    continue;

                // 仅考虑 probe 点（2米间隔）
                if (nx % mixRadius != 0 || ny % mixRadius != 0 || nz % mixRadius != 0)
                    continue;

                SphericalHarmonicsL2 probeSH = gridSH[nx, ny, nz];
                if (IsEmptySH(probeSH))
                    continue;

                Vector3 probePos = new Vector3(nx, ny, nz);
                if (!IsVisible(pos, probePos))
                    continue;

                float dist = Vector3.Distance(pos, probePos);
                float weight = 1f / Mathf.Max(dist, 0.001f);

                AddSH(ref accum, probeSH, weight);
                totalWeight += weight;
            }

            if (totalWeight > 0f)
            {
                Color color = SHUtility.EvaluateL0(accum);
                UnityEngine.Debug.Log("color /= totalWeight, xyz:" + x + "," + y + "," + z + ", color:" + color + "," +
                                      totalWeight);
                NormalizeSH(ref accum, totalWeight);
                gridSH[x, y, z] = accum;
            }
        }
    }
    
    private bool IsEmptySH(SphericalHarmonicsL2 sh)
    {
        return Mathf.Approximately(sh[0, 0], 0f) &&
               Mathf.Approximately(sh[1, 0], 0f) &&
               Mathf.Approximately(sh[2, 0], 0f);
    }

    private void NormalizeSH(ref SphericalHarmonicsL2 sh, float totalWeight)
    {
        if (totalWeight <= 0f) return;
        for (int c = 0; c < 3; c++)
        for (int i = 0; i < 9; i++)
            sh[c, i] /= totalWeight;
    }
    
    // 加权累加 SH 所有通道
    private void AddSH(ref SphericalHarmonicsL2 target, SphericalHarmonicsL2 source, float weight)
    {
        for (int rgb = 0; rgb < 3; rgb++)
        for (int i = 0; i < 9; i++)
            target[rgb, i] += source[rgb, i] * weight;
    }
    
    static void AddSH_L0(ref SphericalHarmonicsL2 target, SphericalHarmonicsL2 source, float weight)
    {
        for (int rgb = 0; rgb < 3; rgb++)
            target[rgb, 0] += source[rgb, 0] * weight;
    }

    // 3x3x3 高斯权重核（归一化系数为 1/64）
    private float[,,] gaussianKernel = new float[,,]
    {
        {
            { 1, 2, 1 },
            { 2, 4, 2 },
            { 1, 2, 1 }
        },
        {
            { 2, 4, 2 },
            { 4, 8, 4 },
            { 2, 4, 2 }
        },
        {
            { 1, 2, 1 },
            { 2, 4, 2 },
            { 1, 2, 1 }
        }
    };
    
    float normalization = 1f / 64f;
    
    public SphericalHarmonicsL2[,,] Gaussian3(SphericalHarmonicsL2[,,] volume, bool onlyL0 = false)
    {
        int sizeX = volume.GetLength(0);
        int sizeY = volume.GetLength(1);
        int sizeZ = volume.GetLength(2);

        var result = new SphericalHarmonicsL2[sizeX, sizeY, sizeZ];

        for (int x = 0; x < sizeX; x++)
        for (int y = 0; y < sizeY; y++)
        for (int z = 0; z < sizeZ; z++)
        {
            SphericalHarmonicsL2 accum = new SphericalHarmonicsL2();

            for (int dx = -1; dx <= 1; dx++)
            for (int dy = -1; dy <= 1; dy++)
            for (int dz = -1; dz <= 1; dz++)
            {
                int nx = x + dx;
                int ny = y + dy;
                int nz = z + dz;

                if (nx < 0 || nx >= sizeX ||
                    ny < 0 || ny >= sizeY ||
                    nz < 0 || nz >= sizeZ)
                    continue;

                float weight = gaussianKernel[dx + 1, dy + 1, dz + 1] * normalization;
                if (onlyL0)
                    AddSH_L0(ref accum, volume[nx, ny, nz], weight);
                else
                    AddSH(ref accum, volume[nx, ny, nz], weight);
            }

            result[x, y, z] = accum;
        }

        return result;
    }
    
    private float[] gaussian1D = new float[] { 1f, 4f, 6f, 4f, 1f }; // sum = 16

    public SphericalHarmonicsL2[,,] Gaussian5(SphericalHarmonicsL2[,,] volume, bool onlyL0 = false)
    {
        int sizeX = volume.GetLength(0);
        int sizeY = volume.GetLength(1);
        int sizeZ = volume.GetLength(2);

        var result = new SphericalHarmonicsL2[sizeX, sizeY, sizeZ];

        for (int x = 0; x < sizeX; x++)
        for (int y = 0; y < sizeY; y++)
        for (int z = 0; z < sizeZ; z++)
        {
            SphericalHarmonicsL2 accum = new SphericalHarmonicsL2();
            float totalWeight = 0f;

            Vector3 pos = new Vector3(x, y, z);
            
            for (int dx = -2; dx <= 2; dx++)
            for (int dy = -2; dy <= 2; dy++)
            for (int dz = -2; dz <= 2; dz++)
            {
                int nx = x + dx;
                int ny = y + dy;
                int nz = z + dz;

                if (nx < 0 || nx >= sizeX ||
                    ny < 0 || ny >= sizeY ||
                    nz < 0 || nz >= sizeZ)
                    continue;

                Vector3 probePos = new Vector3(nx, ny, nz);
                if (!IsVisible(pos,probePos))
                {
                    continue;
                }
                float wx = gaussian1D[dx + 2];
                float wy = gaussian1D[dy + 2];
                float wz = gaussian1D[dz + 2];
                float weight = wx * wy * wz;

                if (onlyL0)
                    AddSH_L0(ref accum, volume[nx, ny, nz], weight);
                else
                    AddSH(ref accum, volume[nx, ny, nz], weight);

                totalWeight += weight;
            }

            // 归一化
            if (totalWeight > 0f)
            {
                for (int c = 0; c < 3; c++)
                {
                    int maxCoeff = onlyL0 ? 1 : 9;
                    for (int i = 0; i < maxCoeff; i++)
                    {
                        accum[c, i] /= totalWeight;
                    }
                }
            }

            result[x, y, z] = accum;
        }

        return result;
    }
    
    public Color[,,] ColorGaussian5(Color[,,] C)
    {
        int sizeX = C.GetLength(0);
        int sizeY = C.GetLength(1);
        int sizeZ = C.GetLength(0);

        Color[,,] result = new Color[sizeX, sizeY, sizeZ];

        for (int x = 0; x < sizeX; x++)
        for (int y = 0; y < sizeY; y++)
        for (int z = 0; z < sizeZ; z++)
        {
            Color accum = new Color();
            float totalWeight = 0f;

            Vector3 pos = new Vector3(x, y, z);
            
            for (int dx = -2; dx <= 2; dx++)
            for (int dy = -2; dy <= 2; dy++)
            for (int dz = -2; dz <= 2; dz++)
            {
                int nx = x + dx;
                int ny = y + dy;
                int nz = z + dz;

                if (nx < 0 || nx >= sizeX ||
                    ny < 0 || ny >= sizeY ||
                    nz < 0 || nz >= sizeZ)
                    continue;

                // Vector3 probePos = new Vector3(nx, ny, nz);
                // if (!IsVisible(pos,probePos))
                // {
                //     continue;
                // }
                float wx = gaussian1D[dx + 2];
                float wy = gaussian1D[dy + 2];
                float wz = gaussian1D[dz + 2];
                float weight = wx * wy * wz;

                accum += C[nx, ny, nz] * weight;
                totalWeight += weight;
            }

            // 归一化
            if (totalWeight > 0f)
            {
                accum /= totalWeight;
            }

            result[x, y, z] = accum;
        }

        return result;
    }
    
    #endregion
}
